#region License

// This file is part of Grawlix.
// 
// Grawlix is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// Grawlix is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public
// License along with Grawlix.  If not, see
// <http://www.gnu.org/licenses/>.

#endregion

using System;

namespace Grawlix.Geometry
{
	/// <summary>
	/// An immutable, efficient 2x2 rotation matrix, stored as sin
	/// and cosine of the rotation angle.
	/// </summary>
	public struct Rotation2
	{
		/// <summary>
		/// The 180 degree rotation about the axis.
		/// </summary>
		public static readonly Rotation2 Degree180 =
			new Rotation2(-1, 0);

		/// <summary>
		/// The 90 degree rotation about the axis.
		/// </summary>
		public static readonly Rotation2 Degree270 =
			new Rotation2(0, -1);

		/// <summary>
		/// The 90 degree rotation about the axis.
		/// </summary>
		public static readonly Rotation2 Degree90 =
			new Rotation2(0, 1);

		/// <summary>
		/// The identity rotation; 0 angle.
		/// </summary>
		public static readonly Rotation2 Identity =
			new Rotation2(0f);

		/// <summary>
		/// The default rotation: not a rotation matrix, but the zero
		/// matrix.
		/// </summary>
		public static readonly Rotation2 Zero = new Rotation2(0, 0);

		/// <summary>
		/// The cosine of the rotation angle.
		/// </summary>
		private readonly float _cos;

		/// <summary>
		/// The sine of the rotation angle.
		/// </summary>
		private readonly float _sin;

		/// <summary>
		/// Initializes the rotation with a given angle.
		/// </summary>
		/// <param name="angle">The counter-clockwise angle this
		/// rotation will apply to a vector or matrix.</param>
		public Rotation2(float angle)
		{
			_cos = (float)Math.Cos(angle);
			_sin = (float)Math.Sin(angle);
		}

		/// <summary>
		/// Directly initializes the matrix.
		/// </summary>
		/// <param name="cosine">The cosine of the angle.</param>
		/// <param name="sine">The sine of the angle.</param>
		private Rotation2(float cosine, float sine)
		{
			_cos = cosine;
			_sin = sine;
		}

		/// <summary>
		/// The counter-clockwise angle that this rotation will apply
		/// to a vector or matrix.
		/// </summary>
		public float Angle
		{
			get { return (float)Math.Atan2(_sin, _cos); }
		}

		/// <summary>
		/// The cosine of the <see cref="Angle"/>.
		/// </summary>
		public float Cos
		{
			get { return _cos; }
		}

		/// <summary>
		/// The sine of the <see cref="Angle"/>.
		/// </summary>
		public float Sin
		{
			get { return _sin; }
		}

		/// <summary>
		/// Indicates whether this instance and a specified object are equal.
		/// </summary>
		/// <returns>
		/// true if <paramref name="obj" /> and this
		/// instance are the same type and represent
		/// the same value; otherwise, false.
		/// </returns>
		/// <param name="obj">Another object to compare to.</param>
		public override bool Equals(object obj)
		{
			return obj.GetType() == typeof(Rotation2) &&
			       Equals((Rotation2)obj);
		}

		/// <summary>
		/// Indicates whether the current object is equal to another
		/// object of the same type.
		/// </summary>
		/// <returns>
		/// true if the current object is equal to the
		/// <paramref name="other" /> parameter; otherwise, false.
		/// </returns>
		/// <param name="other">An object to compare with this object.
		/// </param>
		public bool Equals(Rotation2 other)
		{
			return other._cos == _cos && other._sin == _sin;
		}

		/// <summary>
		/// Directly initializes and returns a matrix.
		/// </summary>
		/// <param name="cos">The cosine of the angle.</param>
		/// <param name="sin">The sine of the angle.</param>
		/// <returns>The newly initialized matrix.</returns>
		/// <exception cref="ArgumentException">
		/// cos and sin do not describe the same angle.</exception>
		public static Rotation2 FromCosSin(float cos, float sin)
		{
			float compare = Math.Abs(cos * cos + sin * sin - 1);
			if (compare > .01)
			{
				throw new ArgumentException(
					"The cosine and sine specified as part of a rotation " +
					"matrix do not describe the same angle. " +
					"Values cos=" + cos + " and sin=" + sin +
					" were specified. " +
					"Use the normal constructor to directly " +
					"specify an angle.");
			}
			return new Rotation2(cos, sin);
		}

		/// <summary>
		/// Returns the hash code for this instance.
		/// </summary>
		/// <returns>
		/// A 32-bit signed integer that is the hash code for this instance.
		/// </returns>
		public override int GetHashCode()
		{
			unchecked
			{
				return (_cos.GetHashCode() * 397) ^ _sin.GetHashCode();
			}
		}

		/// <summary>
		/// Computes the inverse rotation.
		/// </summary>
		/// <returns>The inverse rotation.
		/// </returns>
		public Rotation2 Invert()
		{
			return new Rotation2(_cos, -_sin);
		}

		/// <summary>
		/// Computes the inner product of two rotations.
		/// </summary>
		/// <param name="rhs">The multiplier.</param>
		/// <returns>The inner product of the two rotations.</returns>
		public Rotation2 Multiply(Rotation2 rhs)
		{
			return this * rhs;
		}

		/// <summary>
		/// Computes the inner product of the rotation and a matrix.
		/// </summary>
		/// <param name="rhs">The multiplier.</param>
		/// <returns>The inner product of the rotation and matrix.
		/// </returns>
		public Matrix22 Multiply(Matrix22 rhs)
		{
			return this * rhs;
		}

		/// <summary>
		/// Computes the inner product of the rotation and a vector.
		/// </summary>
		/// <param name="rhs">The multiplier.</param>
		/// <returns>The inner product of the rotation and vector.
		/// </returns>
		public Vector2 Multiply(Vector2 rhs)
		{
			return this * rhs;
		}

		/// <summary>
		/// Obtains a string representation of this object.
		/// </summary>
		/// <returns>A string representation of this object.</returns>
		public override string ToString()
		{
			return ToString(null, null);
		}

		/// <summary>
		/// Formats the value of the current instance using numbers in
		/// the specified format.
		/// </summary>
		/// <returns>
		/// A <see cref="T:System.String" /> containing the value of
		/// the current instance in the specified format.
		/// </returns>
		/// <param name="format">The <see cref="T:System.String" />
		/// specifying the number format to use, or null to specify the
		/// default.
		/// </param>
		/// <param name="formatProvider">The
		/// <see cref="T:System.IFormatProvider" /> to use to format
		/// the value, or null to obtain the numeric format information
		/// from the current locale setting of the operating system. 
		/// </param>
		public string ToString(string format,
		                       IFormatProvider formatProvider)
		{
			return ((Matrix22)this).ToString(format, formatProvider);
		}

		/// <summary>
		/// Determines whether two <see cref="Rotation2"/> objects
		/// have the same value.
		/// </summary>
		/// <param name="left">A <see cref="Rotation2"/>.</param>
		/// <param name="right">A <see cref="Rotation2"/>.</param>
		/// <returns>true if the value of <paramref name="left"/> is the
		/// same as <paramref name="right"/>; else false.</returns>
		public static bool operator ==(
			Rotation2 left, Rotation2 right)
		{
			return left.Equals(right);
		}

		/// <summary>
		/// Implicit conversion from rotation to matrix.
		/// </summary>
		/// <param name="rotation">The rotation to convert.</param>
		/// <returns>The represented matrix.</returns>
		public static implicit operator Matrix22(
			Rotation2 rotation)
		{
			return new Matrix22(rotation._cos, rotation._sin,
			                    -rotation._sin, rotation._cos);
		}

		/// <summary>
		/// Determines whether two <see cref="Rotation2"/> objects
		/// have different values.
		/// </summary>
		/// <param name="left">A <see cref="Rotation2"/>.</param>
		/// <param name="right">A <see cref="Rotation2"/>.</param>
		/// <returns>true if the value of <paramref name="left"/> is different
		/// from <paramref name="right"/>; else false.</returns>
		public static bool operator !=(
			Rotation2 left, Rotation2 right)
		{
			return !left.Equals(right);
		}

		/// <summary>
		/// Computes the inner product of two rotations.
		/// </summary>
		/// <param name="lhs">The multiplicand.</param>
		/// <param name="rhs">The multiplier.</param>
		/// <returns>The inner product of the two rotations.</returns>
		public static Rotation2 operator *(
			Rotation2 lhs, Rotation2 rhs)
		{
			return
				new Rotation2(
					lhs._cos * rhs._cos - lhs._sin * rhs._sin,
					lhs._cos * rhs._sin + lhs._sin * rhs._cos);
		}

		/// <summary>
		/// Computes the inner product of a rotation and a matrix.
		/// </summary>
		/// <param name="lhs">The multiplicand.</param>
		/// <param name="rhs">The multiplier.</param>
		/// <returns>The inner product of the rotation and matrix.
		/// </returns>
		public static Matrix22 operator *(
			Rotation2 lhs, Matrix22 rhs)
		{
			return (Matrix22)lhs * rhs;
		}

		/// <summary>
		/// Computes the inner product of a rotation and a vector.
		/// </summary>
		/// <param name="lhs">The multiplicand.</param>
		/// <param name="rhs">The multiplier.</param>
		/// <returns>The inner product of the rotation and vector.
		/// </returns>
		public static Vector2 operator *(
			Rotation2 lhs, Vector2 rhs)
		{
			return (Matrix22)lhs * rhs;
		}
	}
}